package cn.edu.hit.kg.entity;

import cn.edu.hit.CacheManager;
import cn.edu.hit.ICachedObjectPool;
import cn.edu.hit.core.Document;
import cn.edu.hit.core.db.*;
import cn.edu.hit.core.message.MessageAdapter;
import cn.edu.hit.kg.data.Node;
import com.google.common.collect.ImmutableList;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.stream.Collectors;

public class Fault extends BaseNode implements ISignalMatch {
    static Logger logger = LoggerFactory.getLogger(Fault.class);
    public static final int CACHE_SIZE = 20;

    public Fault(Node node) {
        super(node);
    }

    public static Fault loadById(long id) {
        return (Fault) nodeCache.get(id);
    }

    @Override
    public boolean matchSignal(List<Signal> signal, long timestamp, String unitId) {
        lastUnit = unitId;
        if (lastTimestamp.get(unitId, 0L) == timestamp) {
            return lastMatched.get(unitId, false);
        }

        outMatches.clear();
        outSubIds.clear();
        outUnkonownIds.clear();
        matched = false;
        unknown = false;

        ISignalMatch signalMatch = (ISignalMatch) edgeCache.get(this.getId()).stream()
                .map(id -> nodeCache.get(id))
                .filter(nd -> nd.getNode().hasLabel("Feature") || nd.getNode().hasLabel("FeatureGroup"))
                .findFirst()
                .orElse(null);
//
//        if(this.getId() == 2663){
//            int a = 1;
////            System.out.println("2663");
//        }

        if (signalMatch == null) {
//            logger.error("Fault.java line18,Not a validated Fault:"+this.getId());
            logger.error("故障" + this.getId() + "录入有误");
            return false;
        }

        matched = signalMatch.matchSignal(signal, timestamp, unitId);

        if (matched || signalMatch.isUnKnown()) {
            this.outMatches.addAll(signalMatch.getOutMatches());
            this.outSubIds.addAll(signalMatch.getSubMatched());
            this.outSubIds.add(signalMatch.getId());
            this.outSubIds.add(this.getId());
            this.channelIds.push(unitId, signalMatch.getChannelIds(unitId));

//            System.out.println(this.getId());
//            System.out.println(matched);
//
//            System.out.println("matches:"+this.outMatches.toString());
//            System.out.println("outsubId:"+this.outSubIds.toString());
//            System.out.println("channelID:"+this.channelIds.toString());
            matchedFeature.push(unitId, this.outSubIds.toString());
        }


        if (!matched && signalMatch.isUnKnown()) {
            unknown = true;
            this.outUnkonownIds.add(this.getId());
            this.outUnkonownIds.add(signalMatch.getId());
            this.outUnkonownIds.addAll(signalMatch.getSubUnknown());
            this.outUnkonownIds.removeAll(this.outSubIds);
        }

        Set<Integer> outIds = this.outSubIds.stream()
                .map(nid -> nodeCache.get(nid))
                .filter(om -> !(om instanceof Fault) && om instanceof ISignalMatch)
                .flatMap(om -> ((ISignalMatch) om).getOutIds().stream())
                .collect(Collectors.toSet());

        getAllSubNodes(this.getId()).map(nid -> nodeCache.get(nid))
                .filter(nd -> nd.getNode().hasLabel("Reason"))
                .filter(nd -> {
                    int outId = nd.get("outId", numberTransfer).intValue();
                    return outIds.contains(outId);
                }).forEach(nd -> outSubIds.add(nd.getId()));

        if (matched) {

            List<BaseNode> matched = Fault.getFaultParts(this).stream()
                    .filter(n -> this.outSubIds.stream().anyMatch(id -> id == n.getId()))
                    .collect(Collectors.toList());

            logger.info("匹配：{}", matched);
            String jedis_key = "fault_feature:" + unitId + ":" + this.getId();
            matched.forEach(x -> {
                boolean found = false;
                if (cacheManager.getJedis().exists(jedis_key)) {
                    logger.info("已发生的故障再次发生：{}", jedis_key);
                    long index = cacheManager.getJedis().llen(jedis_key);
                    for (long i = 0; i < index; i++) {
                        String jsonString = cacheManager.getJedis().lindex(jedis_key, i);
                        if (jsonString.contains("\"feature_id\":\"" + x.getId() + "\"")) {
                            JSONObject map = JSONObject.fromObject(jsonString);
                            map.put("appear_times", map.optInt("appear_times", 1) + 1);
                            cacheManager.getJedis().lset(jedis_key, i, map.toString());
                            found = true;
                            break;
                        }
                    }
                }//判断故障是否存在，是则故障发生次数appear_times+1，否则执行下方代码 添加新故障
                if (!found) {
                    JSONObject map = new JSONObject();
                    map.put("machineNumber", unitId);
                    map.put("fault_id", "" + this.getId());
                    map.put("outId", this.getString("outId"));
                    map.put("feature_id", "" + x.getId());
                    map.put("timestamp", "" + timestamp);
                    map.put("appear_times", 1);
                    logger.info("新故障：{}", jedis_key);
//                    System.out.println(freqKey);
                    String freqKey = "feature_frequency:" + unitId + ":" + x.getId();
                    if (cacheManager.getJedis().exists(freqKey)) {
                        logger.info("已经训练过的特征：{}", freqKey);
                        JSONObject freq = JSONObject.fromObject(cacheManager.getJedis().get(freqKey));
                        map.put("quantum", freq.optInt("quantum"));
                        map.put("frequency", freq.getJSONObject("frequency").optInt("" + this.getId()));
                    } else {
                        if (x.node.asMap().containsKey("quantum")) {
                            map.put("quantum", "" + x.getString("quantum"));
                        } else {
                            map.put("quantum", "100");
                        }
                        String freqString = x.getString("frequency");
                        JSONObject jsonObject = JSONObject.fromObject(freqString);
                        if (jsonObject == null || jsonObject.isNullObject()) {
                            logger.info("未训练的特征：{}", freqKey);
                            map.put("frequency", 10);
                        } else {
                            map.put("frequency", "" + jsonObject.optString("" + this.getId(), "10"));
                        }
                    }
                    cacheManager.getJedis().lpush(jedis_key, map.toString());
                }
            });
        }

        if (matched) {
            /**
             * 如果故障不是持续发生
             */
            if (!continuity.get(unitId, false)) {
                String reportId = nextReportId(unitId, lastTimestamp.get(unitId, 0L));
                lastReportId.push(unitId, reportId);
                continuity.push(unitId, true);
                continuityEarliestTimestamp.push(unitId, timestamp);
            }
            /**
             * 故障持续发生
             * 如果时长超过一天24小时，则强行将该故障结束，并作为一条新的故障记录。
             */
            Long start = continuityEarliestTimestamp.get(unitId);
            if ((timestamp - start) > 24 * 60 * 60 * 1000) {
                endTimestamp.push(unitId, timestamp);

                String jedis_key = "fault_feature:" + unitId + ":" + this.getId();
                String newKey = "end_fault:" + lastReportId.get(unitId);
                if (cacheManager.getJedis().exists(jedis_key)) {
                    long index = cacheManager.getJedis().llen(jedis_key);
                    for (long i = 0; i < index; i++) {
                        String jsonString = cacheManager.getJedis().lindex(jedis_key, i);
                        cacheManager.getJedis().lpush(newKey, jsonString);
                    }
                    cacheManager.getJedis().del(jedis_key);
                }
                continuityEarliestTimestamp.push(unitId, 0L);
                continuity.push(unitId, false);
                lastMatched.push(unitId, false);
            } else {
                lastMatched.push(unitId, true);
                endTimestamp.push(unitId, 0L);
            }

        } else {
            if (continuity.get(unitId, false)) {
                /**
                 * 上次发生，这次不发生
                 * 说明故障结束，将表示故障进行中的fault_feature删除，转化成表示故障结束的end_fault
                 */
                endTimestamp.push(unitId, timestamp);

                String jedis_key = "fault_feature:" + unitId + ":" + this.getId();
                String newKey = "end_fault:" + lastReportId.get(unitId);
                if (cacheManager.getJedis().exists(jedis_key)) {
                    long index = cacheManager.getJedis().llen(jedis_key);
                    for (long i = 0; i < index; i++) {
                        String jsonString = cacheManager.getJedis().lindex(jedis_key, i);
                        cacheManager.getJedis().lpush(newKey, jsonString);
                    }
                    cacheManager.getJedis().del(jedis_key);
                }
                continuityEarliestTimestamp.push(unitId, 0L);
            } else {
                /**
                 * 上次不发生，这次不发生
                 */
                continuityEarliestTimestamp.push(unitId, 0L);
            }
            continuity.push(unitId, false);
            lastMatched.push(unitId, false);
        }

        lastTimestamp.push(unitId, timestamp);
//        matchedFeature.push(unitId,outMatches.toString());

        /**
         *  @author 王裕鑫
         *  @date 2020-09-08
         *  调整各原因概率
         */
        updateReasonProp(signal, timestamp, unitId);
        updateRunningIssue(signal, timestamp, unitId);
        updateSolutionProp(signal, timestamp, unitId);
        return matched;
    }

    @Override
    public MatchResultType matchByConclusions(Map<Long, Boolean> conclusions) {
        ISignalMatch signalMatch = (ISignalMatch) edgeCache.get(this.getId()).stream()
                .map(id -> nodeCache.get(id))
                .filter(nd -> nd.getNode().hasLabel("Feature") || nd.getNode().hasLabel("FeatureGroup"))
                .findFirst()
                .orElse(null);
        return signalMatch.matchByConclusions(conclusions);
    }

    @Override
    public MatchTrack trackByConclusions(Map<Long, Boolean> conclusions) {
        MatchTrack track = new MatchTrack();
        track.node = this;
        ISignalMatch signalMatch = (ISignalMatch) edgeCache.get(this.getId()).stream()
                .map(id -> nodeCache.get(id))
                .filter(nd -> nd.getNode().hasLabel("Feature") || nd.getNode().hasLabel("FeatureGroup"))
                .findFirst()
                .orElse(null);
        MatchTrack subTrack = signalMatch.trackByConclusions(conclusions);
        if (signalMatch instanceof FeatureGroup && ((FeatureGroup) signalMatch).getString("combineType").equalsIgnoreCase("or")){
            track.children = subTrack.children;
        }else {
            track.children.add(subTrack);
        }
        track.result = subTrack.result;
        return track;
    }



    public Map matchByConclusionsAsReport(Map<Long, Boolean> conclusions, String unit) {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("id", this.getId());
        MatchResultType matchResultType = this.matchByConclusions(conclusions);
        resultMap.put("isMatched", matchResultType == MatchResultType.TRUE);
        resultMap.put("isUnknown", matchResultType == MatchResultType.UNKNOWN);

//        resultMap.put("reportId", lastReportId.get(unit));
//        if (unitDoc != null) {
//            resultMap.put("platform_id", unitDoc.getString("MachineNo") + String.format("%07d", this.getOutId()));
//        }
//        resultMap.put("last_timestamp", continuityEarliestTimestamp.get(unit, 0L));
//        resultMap.put("end_timestamp", endTimestamp.get(unit, 0L));

        resultMap.put("faultName", this.get("name", stringTransfer));

        resultMap.put("matched", this.getSubMatched());
        resultMap.put("unknown", this.getSubUnknown());

        resultMap.put("parts", this.getString("faultCategory"));

        List<Map<String, Object>> featureMatched =
                getFaultParts(this).stream()
                        .map(node -> {
                            Map<String, Object> map = new HashMap<>();
                            map.put("id", node.getId());
                            String freqKey = "feature_frequency:" + unit + ":" + node.getId();//每个特征的置信度应该与机器相关
                            int quantum = 1, frequency = 0;
                            if (cacheManager.getJedis().exists(freqKey)) {
                                JSONObject freq = JSONObject.fromObject(cacheManager.getJedis().get(freqKey));
                                quantum = freq.optInt("quantum");
                                frequency = freq.getJSONObject("frequency").optInt("" + this.getId());
                            } else {
                                String freqString = node.getString("frequency");
                                try {
                                    quantum = Integer.parseInt(node.getString("quantum"));
                                    frequency = Integer.parseInt(JSONObject.fromObject(freqString).optString("" + this.getId()));
                                } catch (JSONException ex) {
                                    quantum = 100;
                                    frequency = 10;
                                } catch (NumberFormatException ex) {
                                    quantum = 100;
                                    frequency = 10;
                                }
                            }
                            map.put("probability", frequency * 1.0 / quantum);

                            return (map);
                        }).collect(Collectors.toList());  // 计算每一条matched到的特征的置信度
        double fault_probability = 1.0 - featureMatched.stream()
                .mapToDouble(x -> 1.0 - (double) (x.get("probability")))
                .reduce((x, y) -> x * y).orElse(1);
        resultMap.put("probability", fault_probability);
        resultMap.put("matchedFeatures", featureMatched);

        Map<Long,Double> reasonProps = matchReasons(conclusions,unit);
        List<Map<String, Object>> reasonMatched = reasonProps.entrySet().stream()
                .map(entry-> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("id", entry.getKey());
                    map.put("probability", entry.getValue());
                    return map;
                }).collect(Collectors.toList());
        resultMap.put("reasons", reasonMatched);

        RunningIssue runningIssue = matchRunningIssue(conclusions,reasonProps);
        resultMap.put("suggest", runningIssue.getName());

        Map<Long,Double> solutionProps = matchSolution(conclusions,reasonProps);
        List<Map<String, Object>> solutionMatched = solutionProps.entrySet().stream()
                .map(entry-> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("id", entry.getKey());
                    map.put("probability", entry.getValue());
                    return map;
                }).collect(Collectors.toList());
        resultMap.put("solutions", solutionMatched);
        return resultMap;
    }

    public boolean matchSignal(List<Signal> signal, long timestamp) {
        return matchSignal(signal, timestamp, "NotParallel");
    }

    /**
     * @author 王裕鑫
     * @date 2020-09-08
     * 调整原因概率，用于cn.edu.hit.kg.entity.Fault#matchSignal(java.util.List, long, java.lang.String)之后进行
     * @param signal 推理机输入特征
     * @param timestamp 推理时间
     * @param unitId 机组ID
     * @see Fault#matchSignal(java.util.List, long, java.lang.String)
     */
    public void updateReasonProp(List<Signal> signal, long timestamp, String unitId){
        // Step1. 计算该故障从 “故障--原因”边携带的支持度
        ConcurrentHashMap<Long,Double> reasonPropsMap = new ConcurrentHashMap<>();
        String freqKey = "fault_reason:" + unitId + ":" + this.getId();  //每个原因的置信度应该与机器相关
        if (cacheManager.getJedis().exists(freqKey)) {
            JSONObject freq = JSONObject.fromObject(cacheManager.getJedis().get(freqKey));
            BaseNode.getAllSubNodes(this.getId())
                    .map(nodeCache::get)
                    .filter(r -> r.node.hasLabel("Reason") && r.hasSubEdges())
                    .forEach(r -> {
                        int quantum = freq.getInt("quantum");
                        int frequency = freq.optJSONObject("frequency").optInt("" + r.getId());
                        reasonPropsMap.put(r.getId(),frequency * 1.0 / quantum);
                    });
        }
        else {
            BaseNode.getAllSubNodes(this.getId())
                    .map(nodeCache::get)
                    .filter(r -> r.node.hasLabel("Reason") && r.hasSubEdges())
                    .forEach(r -> {
                        int quantum = Integer.parseInt("" + this.node.asMap().getOrDefault("quantum", 100));
                        int frequency = 0;
                        if (this.node.asMap().containsKey("frequency")) {
                            JSONObject freq = JSONObject.fromObject(this.node.asMap().get("frequency"));
                            frequency = freq.optInt("" + r.getId(), 0);
                            reasonPropsMap.put(r.getId(),  frequency * 1.0 / quantum);
                        } else {
                            reasonPropsMap.put(r.getId(), Double.parseDouble(r.getString("probability")));
                        }
                    });
        }

//        System.out.println(reasonPropsMap);


        BaseNode.getAllSubNodes(this.getId())
                .map(nodeCache::get)
                .filter(r -> r.node.hasLabel("Reason") && r.hasSubEdges()
                        && BaseNode.getSubEdges(r.getId()).stream().map(nodeCache::get).anyMatch(f->f instanceof ISignalMatch))
                .forEachOrdered(r->{
                    //Step2. 找出原因连接在一起的各特征ID
                    Set<Long> additionFeatures = BaseNode.getSubEdges(r.getId());

                    JSONObject freq = null;
                    // Step3. Reason节点的frequency 属性存储各个 feature 对其支持度
                    if (r.node.asMap().containsKey("frequency")) {
                        freq = JSONObject.fromObject(r.node.asMap().get("frequency"));
                    }
                    // Step4. 计算各特征，并求出最大的支持度，并与上面的支持度比较取最大值
                    double oldProp = reasonPropsMap.getOrDefault(r.getId(),0.0);
                    double maxProp = oldProp;
                    if (oldProp<1 && freq!=null){ // oldProp=1说明原来的概率已经是1了没有必要再计算“原因--[p]--特征”了
                        JSONObject finalFreq = freq;
                        maxProp =
//                                Math.max(
                                1 - additionFeatures.stream().map(nodeCache::get)
                                .filter(node -> node instanceof ISignalMatch)
                                .map(signalMatch -> ((ISignalMatch) signalMatch).matchSignal(signal, timestamp, unitId)?signalMatch.getNode():null)
                                .filter(Objects::nonNull)
                                .mapToDouble(signalMatch-> finalFreq.optDouble(""+signalMatch.id(),0.0))
                                        .map(x->1-x)
                                        .reduce((x,y)->x*y)
                                .orElse(1.0)
                                ;
//                                ,maxProp);
                    }

                    //TODO 目前只提供 原因--[p]--特征 这一种边，但我个人感觉将来应当提供 原因--[概率增强/降低x倍]--特征 这种边应该更合理
                    //TODO 原因--[p]--特征 的自学习 需要讨论,单纯+1无法解释  与 “原因--故障”(各原因概率和为1,Sum(P(Reason|Fault))=1)  及 “故障--特征”(SUM(P(Falut|Feature)=1)) 都不同

                    //Step5.调整其它原因的概率：得出新概率值后，重新调整其它原因的概率使之总和保持不变; 正常情况下不应当出现有多个 原因--[p]--特征 成立的情况，
                    // 此处对该情况不做处理，概率将按照 最后一个 原因--[p]--特征 调整

//                    System.out.println("Max:"+ maxProp + "\tOld:" + oldProp);
                    if (maxProp != oldProp && maxProp>0){
                        reasonPropsMap.put(r.getId(),maxProp);
//                        for (Long reasonId : reasonPropsMap.keySet()) {
//                            if (reasonId!=r.getId()){
//                                System.out.println(""+reasonId+":\t"+reasonPropsMap.get(reasonId)+"\t"+reasonPropsMap.get(reasonId)*(1-maxProp)/(1-oldProp));
//                                System.out.println(""+reasonId+":\t"+reasonPropsMap.get(reasonId)+"\t"+reasonPropsMap.get(reasonId));
//                                reasonPropsMap.put(reasonId,reasonPropsMap.get(reasonId)*(1-maxProp)/(1-oldProp));
//                                reasonPropsMap.put(reasonId,reasonPropsMap.get(reasonId)*(1-maxProp)/(1-oldProp));
//                            }
//                        }
                    }

                });
        reasonProps.push(unitId,reasonPropsMap);
    }

    public Map<Long,Double> matchReasons(Map<Long, Boolean> conclusions, String unitId){
        ConcurrentHashMap<Long,Double> reasonFaultMap = new ConcurrentHashMap<>(); // P(Reason|Fault)
        ConcurrentHashMap<Long,Double> reasonFeatureMap = new ConcurrentHashMap<>(); // P(Reason|Feature)
        String freqKey = "fault_reason:" + unitId + ":" + this.getId();  //每个原因的置信度应该与机器相关

        List<BaseNode> reasons = BaseNode.getAllSubNodes(this.getId())
                .map(nodeCache::get)
                .filter(r -> r.node.hasLabel("Reason") && r.hasSubEdges()
                        && BaseNode.getSubEdges(r.getId()).stream().map(nodeCache::get).anyMatch(f->f instanceof ISignalMatch))
                .collect(Collectors.toList());

        if (cacheManager.getJedis().exists(freqKey)) {
            JSONObject freq = JSONObject.fromObject(cacheManager.getJedis().get(freqKey));
            BaseNode.getAllSubNodes(this.getId())
                    .map(nodeCache::get)
                    .filter(r -> r.node.hasLabel("Reason") && r.hasSubEdges())
                    .forEach(r -> {
                        int quantum = freq.getInt("quantum");
                        int frequency = freq.optJSONObject("frequency").optInt("" + r.getId());
                        reasonFaultMap.put(r.getId(),frequency * 1.0 / quantum);
                    });
        }
        else {
                    reasons
                    .forEach(r -> {
                        int quantum = Integer.parseInt("" + this.node.asMap().getOrDefault("quantum", 100));
                        int frequency = 0;
                        if (this.node.asMap().containsKey("frequency")) {
                            JSONObject freq = JSONObject.fromObject(this.node.asMap().get("frequency"));
                            frequency = freq.optInt("" + r.getId(), 0);
                            reasonFaultMap.put(r.getId(),  frequency * 1.0 / quantum);
                        } else {
                            reasonFaultMap.put(r.getId(), Double.parseDouble(r.getString("probability")));
                        }
                    });
        }

        reasons.forEach(r -> {
                    double selfFrequency = Double.parseDouble("" + r.node.asMap().getOrDefault("selfFrequency", reasonFaultMap.get(r.getId())));
                    reasonFeatureMap.put(r.getId(),selfFrequency);
                });

        System.out.println("reasonFaultMap = " + reasonFaultMap);
        System.out.println("reasonFeatureMap = " + reasonFeatureMap);
        double p = 0;
        ConcurrentHashMap<Long,Double> reasonSubMap = new ConcurrentHashMap<>();
        for (BaseNode reason : reasons) {
            Set<Long> additionFeatures = BaseNode.getSubEdges(reason.getId());
            boolean matched = additionFeatures.stream().map(nodeCache::get)
                    .filter(node -> node instanceof ISignalMatch)
                    .anyMatch(match->((ISignalMatch) match).matchByConclusions(conclusions) == MatchResultType.TRUE);
            if (matched){
                System.out.println("reason = " + reason.getId());
                for (Long reasonId : reasonFaultMap.keySet()) {
                    if (reasonId == reason.getId()){
                        double pp = reasonFaultMap.get(reasonId) * reasonFeatureMap.get(reasonId);
                        p += pp;
                        reasonSubMap.put(reasonId,reasonSubMap.getOrDefault(reasonId,0.0) + pp);
                    }else {
                        double pp = reasonFaultMap.get(reasonId) * (1 - reasonFeatureMap.get(reason.getId()));
                        p += pp;
                        reasonSubMap.put(reasonId,reasonSubMap.getOrDefault(reasonId,0.0) + pp);
                    }
                }
            }
        }
        System.out.println("p = " + p);
        System.out.println("reasonSubMap = " + reasonSubMap);
        if (p==0){
            return reasonFaultMap;
        }
        for (Long reasonId : reasonSubMap.keySet()) {
            reasonSubMap.put(reasonId,reasonSubMap.getOrDefault(reasonId,0.0) / p);
        }
        return reasonSubMap;
    }

    public void updateRunningIssue(List<Signal> signals, long timestamp, String unitId) {
        ConcurrentHashMap<Long,Double> reasonPropsMap = reasonProps.get(unitId);

        //part1. 获取已成立的 Reason下的Issue
        RunningIssue issue =
            reasonPropsMap.entrySet().stream()
//                .filter(entry -> entry.getValue()>=0.5)
                .flatMap(entry ->
                    BaseNode.getSubEdges(entry.getKey()).stream()
                            .map(id->nodeCache.get(id))
                            .filter(node->node.node.hasLabel("RunningIssue"))
                ).filter(i->!i.hasSubEdges() ||
                BaseNode.getSubEdges(i.getId()).stream()
                        .map(nodeCache::get)
                        .noneMatch(f->f instanceof ISignalMatch))
                .map(i->i.getString("suggest"))
                .map(RunningIssue::asIssue)
                .max(Comparator.comparing(RunningIssue::getLevel))
                .orElse(RunningIssue.LEVEL_0);

        //part2. 获取任意的下接特征的Issue
        if (issue!=RunningIssue.LEVEL_2){
            List<Pair<BaseNode,RunningIssue>> list = BaseNode.getAllSubNodes(this.getId())
                    .map(nodeCache::get)
                    .filter(r -> r.node.hasLabel("RunningIssue") && r.hasSubEdges()
                            && BaseNode.getSubEdges(r.getId()).stream().map(nodeCache::get).anyMatch(f->f instanceof ISignalMatch))
                    .map(node->new Pair<>(node,RunningIssue.asIssue(node.getString("suggest"))))
//                    .filter(pair->pair.getValue().getLevel()>issue.getLevel())
                    .collect(Collectors.toList());
            for (Pair<BaseNode, RunningIssue> pair : list) {
                //Step2. 找出原因连接在一起的各特征ID
                Set<Long> additionFeatures = BaseNode.getSubEdges(pair.getKey().getId());
                if(issue.getLevel()<pair.getValue().getLevel() && additionFeatures.stream().map(nodeCache::get)
                        .filter(node -> node instanceof ISignalMatch)
                        .anyMatch(signalMatch -> ((ISignalMatch) signalMatch).matchSignal(signals, timestamp, unitId))
                ){
                    issue = pair.getValue();
                }
            }
        }

        runningIssue.push(unitId,issue);
    }

    public RunningIssue matchRunningIssue(Map<Long, Boolean> conclusions,Map<Long,Double> reasonPropsMap){
        RunningIssue issue =
                reasonPropsMap.entrySet().stream()
                .filter(entry -> entry.getValue()>=0.5)
                        .flatMap(entry ->
                                BaseNode.getSubEdges(entry.getKey()).stream()
                                        .map(id->nodeCache.get(id))
                                        .filter(node->node.node.hasLabel("RunningIssue"))
                        ).filter(i->!i.hasSubEdges() ||
                        BaseNode.getSubEdges(i.getId()).stream()
                                .map(nodeCache::get)
                                .noneMatch(f->f instanceof ISignalMatch))
                        .map(i->i.getString("suggest"))
                        .map(RunningIssue::asIssue)
                        .max(Comparator.comparing(RunningIssue::getLevel))
                        .orElse(RunningIssue.LEVEL_0);

        //part2. 获取任意的下接特征的Issue
        if (issue!=RunningIssue.LEVEL_2){
            List<Pair<BaseNode,RunningIssue>> list = BaseNode.getAllSubNodes(this.getId())
                    .map(nodeCache::get)
                    .filter(r -> r.node.hasLabel("RunningIssue") && r.hasSubEdges()
                            && BaseNode.getSubEdges(r.getId()).stream().map(nodeCache::get).anyMatch(f->f instanceof ISignalMatch))
                    .map(node->new Pair<>(node,RunningIssue.asIssue(node.getString("suggest"))))
//                    .filter(pair->pair.getValue().getLevel()>issue.getLevel())
                    .collect(Collectors.toList());
            for (Pair<BaseNode, RunningIssue> pair : list) {
                //Step2. 找出原因连接在一起的各特征ID
                Set<Long> additionFeatures = BaseNode.getSubEdges(pair.getKey().getId());
                if(issue.getLevel()<pair.getValue().getLevel() && additionFeatures.stream().map(nodeCache::get)
                        .filter(node -> node instanceof ISignalMatch)
                        .anyMatch(signalMatch -> ((ISignalMatch) signalMatch).matchByConclusions(conclusions) == MatchResultType.TRUE)
                ){
                    issue = pair.getValue();
                }
            }
        }
        return issue;
    }

    public void updateSolutionProp(List<Signal> signals, long timestamp, String unitId) {
        ConcurrentHashMap<Long,Double> reasonPropsMap = reasonProps.get(unitId);
        ConcurrentHashMap<Long,Double> solutionPropsMap = new ConcurrentHashMap<>();

        // key =  (reasonId << 4) ^ solutionId
        Map<Long,Double> reasonSolutionMatrix = new HashMap<>();
        Map<Long,Double> featureSolutionMatrix = new HashMap<>();

        List<BaseNode> solutions =  BaseNode.getAllSubNodes(this.getId())
                .map(nodeCache::get)
                .filter(r -> r.node.hasLabel("Solution"))
                .collect(Collectors.toList());
        for (BaseNode solution : solutions) {
            JSONObject freq = null;
            if (solution.node.asMap().containsKey("frequency")) {
                freq = JSONObject.fromObject(solution.node.asMap().get("frequency"));
                for (Object key : freq.keySet()) {
                    if (StringUtils.isNumeric(key.toString())){
                        long featureId = Long.parseLong(key.toString());
                        featureSolutionMatrix.put((featureId<<16)^solution.getId(),freq.optDouble(key.toString(),0));
                    }
                }
            }
            if (solution.node.asMap().containsKey("reasonFrequency")) {
                freq = JSONObject.fromObject(solution.node.asMap().get("reasonFrequency"));
                for (Object key : freq.keySet()) {
                    if (StringUtils.isNumeric(key.toString())){
                        long featureId = Long.parseLong(key.toString());
                        reasonSolutionMatrix.put((featureId<<16)^solution.getId(),freq.optDouble(key.toString(),0));
                    }
                }
            }
        }

        for (BaseNode solution : solutions) {
            Set<Long> additionFeatures = BaseNode.getSubEdges(solution.getId());
            double freq = additionFeatures.stream().map(nodeCache::get)
                    .filter(node -> node instanceof ISignalMatch)
                    .map(signalMatch -> ((ISignalMatch) signalMatch).matchSignal(signals, timestamp, unitId)?signalMatch:null)
                    .filter(Objects::nonNull)
                    .mapToDouble(signalMatch-> featureSolutionMatrix.getOrDefault((((ISignalMatch) signalMatch).getId()<<16)^solution.getId(),0.0))
//                    .peek(ratio->{
//                        System.out.printf("%d\t%f\n",solution.getId(),ratio);
//                    })
                    .map(x->1-x)
                    .reduce(1.0,(x,y)->x*y);
//            System.out.printf("%d\t%f\n",solution.getId(),freq);
            freq *= reasonSolutionMatrix.keySet().stream()
//                    .peek(key->{
//                        System.out.println("key = " + key);
//                        System.out.println("key&solution = " + (key&solution.getId()));
//                    })
                    .filter(key->(key&solution.getId()&(1<<16-1)) == (solution.getId()&(1<<16-1)))
                    .mapToDouble(key->{
                        long reasonId = (key ^ solution.getId()) >> 16;
                        Double reasonProp = reasonPropsMap.getOrDefault(reasonId, 0.0);
                        Double rsProp = reasonSolutionMatrix.getOrDefault(key, 0.0);
//                        System.out.printf("%d\t%d\t%f\t%f\n",solution.getId(),reasonId,reasonProp,rsProp);
                        return reasonProp * rsProp;
                    }).map(x->1-x)
                    .reduce(1.0,(x,y)->x*y);
//            if (freq!=1){
                solutionPropsMap.put(solution.getId(),1-freq);
//            }
        }

        solutionProps.push(unitId,solutionPropsMap);
    }

    public Map<Long,Double> matchSolution(Map<Long, Boolean> conclusions,Map<Long,Double> reasonPropsMap){
        ConcurrentHashMap<Long,Double> solutionPropsMap = new ConcurrentHashMap<>();

        // key =  (reasonId << 4) ^ solutionId
        Map<Long,Double> reasonSolutionMatrix = new HashMap<>();
        Map<Long,Double> featureSolutionMatrix = new HashMap<>();

        List<BaseNode> solutions =  BaseNode.getAllSubNodes(this.getId())
                .map(nodeCache::get)
                .filter(r -> r.node.hasLabel("Solution"))
                .collect(Collectors.toList());
        for (BaseNode solution : solutions) {
            JSONObject freq = null;
            if (solution.node.asMap().containsKey("frequency")) {
                freq = JSONObject.fromObject(solution.node.asMap().get("frequency"));
                for (Object key : freq.keySet()) {
                    if (StringUtils.isNumeric(key.toString())){
                        long featureId = Long.parseLong(key.toString());
                        featureSolutionMatrix.put((featureId<<16)^solution.getId(),freq.optDouble(key.toString(),0));
                    }
                }
            }
            if (solution.node.asMap().containsKey("reasonFrequency")) {
                freq = JSONObject.fromObject(solution.node.asMap().get("reasonFrequency"));
                for (Object key : freq.keySet()) {
                    if (StringUtils.isNumeric(key.toString())){
                        long featureId = Long.parseLong(key.toString());
                        reasonSolutionMatrix.put((featureId<<16)^solution.getId(),freq.optDouble(key.toString(),0));
                    }
                }
            }
        }

        for (BaseNode solution : solutions) {
            Set<Long> additionFeatures = BaseNode.getSubEdges(solution.getId());
            double freq = additionFeatures.stream().map(nodeCache::get)
                    .filter(node -> node instanceof ISignalMatch)
                    .filter(signalMatch -> ((ISignalMatch) signalMatch).matchByConclusions(conclusions)==MatchResultType.TRUE)
                    .mapToDouble(signalMatch-> featureSolutionMatrix.getOrDefault((((ISignalMatch) signalMatch).getId()<<16)^solution.getId(),0.0))
//                    .peek(ratio->{
//                        System.out.printf("%d\t%f\n",solution.getId(),ratio);
//                    })
                    .map(x->1-x)
                    .reduce(1.0,(x,y)->x*y);
//            System.out.printf("%d\t%f\n",solution.getId(),freq);
            freq *= reasonSolutionMatrix.keySet().stream()
//                    .peek(key->{
//                        System.out.println("key = " + key);
//                        System.out.println("key&solution = " + (key&solution.getId()));
//                    })
                    .filter(key->(key&solution.getId()&(1<<16-1)) == (solution.getId()&(1<<16-1)))
                    .mapToDouble(key->{
                        long reasonId = (key ^ solution.getId()) >> 16;
                        Double reasonProp = reasonPropsMap.getOrDefault(reasonId, 0.0);
                        Double rsProp = reasonSolutionMatrix.getOrDefault(key, 0.0);
//                        System.out.printf("%d\t%d\t%f\t%f\n",solution.getId(),reasonId,reasonProp,rsProp);
                        return reasonProp * rsProp;
                    }).map(x->1-x)
                    .reduce(1.0,(x,y)->x*y);
//            if (freq!=1){
            solutionPropsMap.put(solution.getId(),1-freq);
//            }
        }
        return solutionPropsMap;
    }

    static DataConnection connection = DataConnectionLoader.getInstance();
    static DataReader reader = connection.getReader();
    static DBReadHelper dbHelper = new DBReadHelper(reader);

    public boolean isFinished(String unit) {
        return endTimestamp.get(unit, 0L) != 0L;
    }

    public static List<BaseNode> getFaultParts(BaseNode current) {
        if (current instanceof Feature) {
            return Collections.singletonList(current);
        }
        if (current instanceof FeatureGroup && !current.getString("combineType").equalsIgnoreCase("or")) {
            return Collections.singletonList(current);
        }
        return BaseNode.getSubEdges(current.getId()).stream()
                .flatMap(id -> getFaultParts(BaseNode.loadNodeById(id)).stream())
                .collect(Collectors.toList());
    }

    //将故障信息转化成平台所需json格式
    public Map transferFaultToMap(String unit) {
        Map<String, Object> resultMap = new HashMap<>();

        List<Document> list = reader.queryByTemplate(Filters.and(Filters.eq("MachineNo", unit), Filters.eq("type", "generator")));
        Document unitDoc = list.stream().findFirst().orElse(null);

        //故障编号
        resultMap.put("id", this.getId());
        resultMap.put("isMatched", lastMatched.get(unit, false));
        resultMap.put("isUnknown", unknown);

        resultMap.put("reportId", lastReportId.get(unit));
        if (unitDoc != null) {
            resultMap.put("platform_id", unitDoc.getString("MachineNo") + String.format("%07d", this.getOutId()));
        }
        resultMap.put("last_timestamp", continuityEarliestTimestamp.get(unit, 0L));
        resultMap.put("end_timestamp", endTimestamp.get(unit, 0L));

        resultMap.put("faultName", this.get("name", stringTransfer));
        resultMap.put("suggest", runningIssue.get(unit,RunningIssue.LEVEL_0).getName());

        resultMap.put("matched", this.getSubMatched());
        resultMap.put("unknown", this.getSubUnknown());

        resultMap.put("parts", this.getString("faultCategory"));

        List<Map<String, Object>> featureMatched =
                getFaultParts(this).stream()
                        .map(node -> {
                            Map<String, Object> map = new HashMap<>();
                            map.put("id", node.getId());
                            String freqKey = "feature_frequency:" + unit + ":" + node.getId();//每个特征的置信度应该与机器相关
                            int quantum = 1, frequency = 0;
                            if (cacheManager.getJedis().exists(freqKey)) {
                                JSONObject freq = JSONObject.fromObject(cacheManager.getJedis().get(freqKey));
                                quantum = freq.optInt("quantum");
                                frequency = freq.getJSONObject("frequency").optInt("" + this.getId());
                            } else {
                                String freqString = node.getString("frequency");
                                try {
                                    quantum = Integer.parseInt(node.getString("quantum"));
                                    frequency = Integer.parseInt(JSONObject.fromObject(freqString).optString("" + this.getId()));
                                } catch (JSONException ex) {
                                    quantum = 100;
                                    frequency = 10;
                                } catch (NumberFormatException ex) {
                                    quantum = 100;
                                    frequency = 10;
                                }
                            }
                            map.put("probability", frequency * 1.0 / quantum);

                            return (map);
                        }).collect(Collectors.toList());  // 计算每一条matched到的特征的置信度
        double fault_probability = 1.0 - featureMatched.stream()
                .mapToDouble(x -> 1.0 - (double) (x.get("probability")))
                .reduce((x, y) -> x * y).orElse(1);
        resultMap.put("probability", fault_probability);
        resultMap.put("matchedFeatures", featureMatched);

//        @date 2020-09-08  @author 王裕鑫 修改原因的概率输出方式
//        String freqKey = "fault_reason:" + unit + ":" + this.getId();  //每个原因的置信度应该与机器相关
//        List<Map<String, Object>> reasonMatched;
//        if (cacheManager.getJedis().exists(freqKey)) {
//            JSONObject freq = JSONObject.fromObject(cacheManager.getJedis().get(freqKey));
//            reasonMatched = BaseNode.getAllSubNodes(this.getId())
//                    .map(BaseNode::loadNodeById)
//                    .filter(r -> r.node.hasLabel("Reason"))
//                    .map(r -> {
//                        Map<String, Object> map = new HashMap<>();
//                        map.put("id", r.getId());
//                        int quantum = freq.getInt("quantum");
//                        int frequency = freq.optJSONObject("frequency").optInt("" + r.getId());
//                        map.put("probability", frequency * 1.0 / quantum);
//                        return map;
//                    }).collect(Collectors.toList());
//        } else {
//            reasonMatched = BaseNode.getAllSubNodes(this.getId())
//                    .map(BaseNode::loadNodeById)
//                    .filter(r -> r.node.hasLabel("Reason"))
//                    .map(r -> {
//                        Map<String, Object> map = new HashMap<>();
//                        map.put("id", r.getId());
//                        int quantum = Integer.parseInt("" + this.node.asMap().getOrDefault("quantum", 100));
//                        int frequency = 0;
//                        if (this.node.asMap().containsKey("frequency")) {
//                            JSONObject freq = JSONObject.fromObject(this.node.asMap().get("frequency"));
//                            frequency = freq.optInt("" + r.getId(), 0);
//                        } else {
//                            map.put("probability", Double.parseDouble(r.getString("probability")));
//                            return map;
//                        }
//                        map.put("probability", frequency * 1.0 / quantum);
//                        return map;
//                    }).collect(Collectors.toList());
//        }

        /*
         * @author 王裕鑫
         * @date 2020-09-08
         */
        List<Map<String, Object>> reasonMatched = reasonProps.get(unit).entrySet().stream()
                .map(entry-> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("id", entry.getKey());
                    map.put("probability", entry.getValue());
                    return map;
                }).collect(Collectors.toList());
        resultMap.put("reasons", reasonMatched);

        List<Map<String, Object>> solutionMatched = solutionProps.get(unit).entrySet().stream()
                .map(entry-> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("id", entry.getKey());
                    map.put("probability", entry.getValue());
                    return map;
                }).collect(Collectors.toList());
        resultMap.put("solutions", solutionMatched);

        if (this.getChannelIds(unit) != null)
            resultMap.put("signalPorts", this.getChannelIds(unit).parallelStream().filter(Objects::nonNull).map(portId -> {
                Map map = new HashMap();
                Document doc = dbHelper.getById(portId);
                map.put("id", doc.getObjectId("_id").toString());
                map.put("name", doc.getString("portName"));
                map.put("data_type", doc.getString(MessageAdapter.DATA_SOURCE));
                map.put("data_args", doc.get(MessageAdapter.DATA_SOURCE_ARGS).toString());
                return map;
            }).collect(Collectors.toList()));
        return resultMap;
    }

    public Map transferFaultToMap() {
        return transferFaultToMap(lastUnit);
    }

    static int todayFaultIndex = 1;

    //TODO 编号重置的问题
    public static String nextReportId(String prefix, long timestamp) {
        Date date = new Date(timestamp);
        return String.format("%s%tY%tm%td%04d", prefix, date, date, date, todayFaultIndex++);
    }

    @Override
    public List<Integer> getOutIds() {
        return Collections.singletonList(this.getOutId());
    }

    public int getOutId() {
        return this.get("outId", numberTransfer).intValue();
    }

    private boolean matched = false;
    private boolean unknown = false;
    private String lastUnit;

    public static final CacheManager cacheManager = CacheManager.getInstance();
    private ICachedObjectPool<String, String> lastReportId = cacheManager.createCachedObjectPool("Fault.lastReportId." + this.getId());
    private ICachedObjectPool<String, Boolean> lastMatched = cacheManager.createCachedObjectPool("Fault.lastMatched." + this.getId());
    private ICachedObjectPool<String, Long> lastTimestamp = cacheManager.createCachedObjectPool("Fault.lastTimestamp." + this.getId());
    private ICachedObjectPool<String, Long> endTimestamp = cacheManager.createCachedObjectPool("Fault.endTimestamp." + this.getId());
    private ICachedObjectPool<String, Boolean> continuity = cacheManager.createCachedObjectPool("Fault.continuity." + this.getId());
    private ICachedObjectPool<String, Long> continuityEarliestTimestamp = cacheManager.createCachedObjectPool("Fault.continuityEarliestTimestamp." + this.getId());
    private ICachedObjectPool<String, String> matchedFeature = cacheManager.createCachedObjectPool("Fault.matchedFeature." + this.getId());
    /*
     * @author 王裕鑫
     * @date 2020-09-08
     * 原因的概率缓存
     */
    private ICachedObjectPool<String,ConcurrentHashMap<Long,Double>> reasonProps = cacheManager.createCachedObjectPool("Fault.reasonProps."+this.getId());
    private ICachedObjectPool<String,ConcurrentHashMap<Long,Double>> solutionProps = cacheManager.createCachedObjectPool("Fault.solutionProps."+this.getId());
    private ICachedObjectPool<String,RunningIssue> runningIssue = cacheManager.createCachedObjectPool("Fault.runningIssue."+this.getId());

    @Override
    public boolean isMatched() {
        return matched;
    }

    public Long getLastTimestamp(String unitId) {
        return continuityEarliestTimestamp.get(unitId);
    }

    public String getReportId(String unitId) {
        return lastReportId.get(unitId);
    }

    public String getMatchedFeature(String unitId) {
        return matchedFeature.get(unitId);
    }

    @Override
    public boolean isUnKnown() {
        return unknown;
    }

    public int generateNumber() {
        int resultSize = tableSize.get(getOutId());
        int[] arr = new int[resultSize + 2];

        arr[0] = 1;
        if (matched) {
            arr[1] = 1;
            for (ISignalMatch signalMatch : this.outMatches) {
                if (signalMatch.isMatched()) {
                    for (Integer matchedId : signalMatch.getOutIds()) {
                        arr[matchedId + 1] = 1;
                    }
                }
            }
        }

        return Arrays.stream(arr).reduce((x, y) -> x * 2 + y).orElse(0);
    }

    static final Map<Integer, Integer> tableSize = new HashMap<>();

    static {
        tableSize.put(1, 4);
        tableSize.put(2, 4);
        tableSize.put(3, 2);
        tableSize.put(4, 2);
        tableSize.put(5, 2);
        tableSize.put(6, 2);
    }

    private Set<ISignalMatch> outMatches = new HashSet<>();
    private Set<Long> outSubIds = new ConcurrentSkipListSet<>();
    private Set<Long> outUnkonownIds = new ConcurrentSkipListSet<>();

    @Override
    public List<ISignalMatch> getOutMatches() {
        return ImmutableList.copyOf(outMatches);
    }

    @Override
    public List<Long> getSubMatched() {
        return ImmutableList.copyOf(outSubIds);
    }

    @Override
    public List<Long> getSubUnknown() {
        return ImmutableList.copyOf(outUnkonownIds);
    }

    private ICachedObjectPool<String, HashSet<String>> channelIds = cacheManager.createCachedObjectPool("Fault.channelIds." + this.getId());

    @Override
    public HashSet<String> getChannelIds(String unitId) {
        return channelIds.get(unitId);
    }

    @Override
    public Set<Feature> getFeatureSet() {
        return edgeCache.get(this.getId()).stream()
                .map(nodeCache::get)
                .filter(om -> !(om instanceof Fault) && om instanceof ISignalMatch)
                .flatMap(node->((ISignalMatch) node).getFeatureSet().stream())
                .collect(Collectors.toSet());
    }
}
